C++ Basics

Exception Safety via try and catch mechanism

try and catch are the most important keywords in C++ as far as implementing exception safety goes. To make statements exception safe, you enclose them within a try block and handle the exceptions that emerge out of the try block in the catch block.

You use new to allocate new memory blocks. The most frequently used form of new returns a pointer to the requested memory if successful or else throws an std::bad_alloc exception.

In the code below we have defined a method that allocates memory based on the given parameter. The code is exception safe since the new operator is in a try block and a catch mechanism for std::bad_alloc exception type.

# include <iostream>    // std::cout, std::endl
# include <new>         // std::bad_alloc

void foo(int size)
{
    std::cout << "Start allocating!" << std::endl;  
    try
    {
        int* myarray= new int[size];

        // Show allocation size
        std::cout << "Allocated: " 
                  << (size*sizeof(int)) 
                  << " bytes" << std::endl;

        delete[] myarray;
    }
    catch (std::bad_alloc& ba)
    {
        std::cerr << "Exception in foo(" << size << "): " 
                  << ba.what() << std::endl;
    }
    std::cout << "All done!" << std::endl;
}

If someone uses the foo(int) function and gives unusual values as input, then the exception will occur.

int main()
{
    foo(5);
    foo(-1); 
    return 0;
}

The output on the console will be:

Start allocating!
Allocated: 20 bytes
All done!
Start allocating!
Exception in foo(-1): std::bad_alloc
All done!

The first call to out foo(int) function is executed without encountering any exceptions, but the second call we find there has been an exception. In the absence of the exception handler, the program would encounter a very ugly end. But thanks to the exception handler, you see that the output displays a decent message. Here is what the console will had looked like if we didn't use exception handling.

Start allocating!
Allocated: 20 bytes
All done!
Start allocating!
terminate called after throwing an instance of 'std::bad_alloc'
   what():  std::bad_alloc
Aborted (core dumped)

Whether an exception is caught or not the rest of the function is still executed as we can see that the message "All done!" is shown in both cases. Only when there is a unhandled exception will the execution of the method stop and the program will exit abnormally.

The exception in the example above was thrown from the C++ Standard Library. Such exceptions are of a known type, and catching a particular type is better as you can pin-point the reason for the exception, do better cleanup, or at least show a precise message to the user.

All exceptions thrown by components of the C++ Standard library are described below:

exception description
bad_alloc thrown by new on allocation failure
bad_cast thrown by dynamic_cast when it fails in a dynamic cast
bad_exception thrown by certain dynamic exception specifiers
bad_typeid thrown by typeid
bad_function_call thrown by empty function objects
bad_weak_ptr thrown by shared_ptr when passed a bad weak_ptr

What happens if a block of code can throw multiple types of exceptions? The exception handling of C++ allows us to use multiple catch statements to a try block. The code below shows the use of multiple catch blocks:

# include <iostream>     // std::cout, std::endl
# include <vector>       // std::vector
# include <stdexcept>    // std::out_of_range


// the 1st parameter will determine the size of the container
// the 2nd parameter will specify the value stored in all integers of the container
// the 3rd parameter will specify the location of the integer to return
int foo(int a, int b, int c)
{
    std::vector<int> intlist;    // declare a vector of integers

    intlist.assign (a,b);  // the assign method will create new content 
                           // for the vector
                           // the first argument tells how many integers
                           // to create and the second argument is the value 
                           // to fill the vector

    std::cout << "Assignment successful!" << std::endl;

    return intlist.at(c);        // return the integer at the given location
}

int main()
{
    int integer = 0;

    try
    {
        integer = foo(4, 15, 6);
    }
    catch(std::bad_alloc& ba)
    {
        std::cerr << "Bad allocation exception: " 
              << ba.what() << std::endl;
    }
    catch(std::out_of_range& oor)
    {
        std::cerr << "Out of range exception: " 
              << oor.what() << std::endl;
    }

    std::cout << "Integer value: " << integer << std::endl;

    return 0;
}

The output of the code:

Assignment successful!
Out of range exception: vector::_M_range_check
Integer value: 0

Notice that the appropriate catch block is executed based on the exception type. Also to note is that the value of the integer variable is still 0 at the end of execution. That's because the execution of the lines in the try block cease as soon as an exception is thrown, and control will be transferred to the appropriate catch block. Which catch block depends on the type of the object thrown. To better illustrate this behavior, let's see what the output of the console is if the parameters are -1, 15 and 6:

Bad allocation exception: std::bad_alloc
Integer value: 0

Now the "Assignment successful!" message is not shown because an exception is caught before the assign method is finished.

If the values sent to the foo(int, int, int) method were 10, 15, and 6, the output on the console will be:

Assignment succesfull!
Integer value: 15

What happens if you don't know the type of exception that will be thrown? There is a general catch handler that will catch any exception. This is usually used when you don't know the type of exception that will be thrown or you don't care about the object that was thrown.

try
{
    int* myarray= new int[size];
    delete[] myarray;
}
catch ( ... ) // catch any type of exception
{
    std::cerr << "Exception caught" << std::endl;
}